#pragma once

#include <iostream>
#include <string>
#include <map>
#include <vector>
#include <stdio.h>
#include <math.h>
#include <armadillo>

#include "Metric.h"

using namespace arma;

using namespace std;

namespace mygalib
{
	class Multivector
	{
	private:
		//The HashMap data structure that represents the basis and the coefficient
		//(Ex: coeff[3] = 3   =>   '3 e1 ^ e2')
		map<unsigned int, double> coeff;

		//Parse a unsigned integer to a string based visualization (Ex: 3 => 'e1 ^ e2')
		static string bitmap_to_string(unsigned int bitmap);

		//Meet and Join Algorithm
		vector<Multivector> _meet_join (Multivector A, Multivector B, int dim);

	public:
		//The Default constructor of a Multivector
		Multivector();

		//A basis constructor. You have to pass the unsigned integer that represents the basis
		Multivector(unsigned int basis);

		//A basis constructor. You have to pass the a binary string 
		//Ex: "101" corresponds to e3^e1
		//    "10" corresponds to e2
		Multivector(string basis);

		//Return the highest grade of a Multivector
		int grade();

		Multivector rp(Multivector& b, Multivector& I);

		//Return the Outer Product between 2 multivectors ( using the ^ Operator Overloading) 
		Multivector operator^(Multivector& b); 

		//Return the Outer Product between 2 multivectors
		Multivector op(Multivector& b);    

		//Return the addition of 2 multivectors ( using the + Operator Overloading)
		Multivector operator+(Multivector& b);

		//Return the addition of 2 multivectors
		Multivector add(Multivector& b);

		//Return the subtraction of 2 multivectors ( using the - Operator Overloading)
		Multivector operator-(Multivector& b);

		//Return the subtraction of 2 multivectors
		Multivector sub(Multivector& b);

		//Return the multiplication of a multivectors and a scalar ( using the * operator Overloading)
		Multivector operator*(double b);

		//Return the multiplication of a multivectors and a scalar
		Multivector mul(double b);

		//Return the division of a multivectors and a scalar  (using the / operator Overloading)
		Multivector operator/(double b);

		//Return the division of a multivectors and a scalar
		Multivector div(double b);

		//Return the Regressive Product between 2 multivectors
		Multivector rp(Multivector& b, int n);

		//Return the Geometric Product between 2 multivectors
		Multivector gp(Multivector& b);   

		//Return the Geometric Product between 2 multivectors
		Multivector operator*(Multivector& b);

		//Multivector Geometric Product
		Multivector ggp(Multivector& b);

		//Return the Left Contration between 2 multivectors
		Multivector lc(Multivector& b);  

		//Return the Left Contration between 2 multivectors
		Multivector operator<<(Multivector& b);  

		//Return the Right Contration between 2 multivectors
		Multivector rc(Multivector& b);    

		//Return the Right Contration between 2 multivectors
		Multivector operator>>(Multivector& b);  

		//Return the Scalar between 2 multivectors
		double sc(Multivector& b);

		//Return the Delta Product between 2 multivectors
		Multivector dp(Multivector& b);    

		//Return the Inverse of a multivector
		Multivector inv();

		//Return the Reverse of a multivector
		Multivector rev();

		//Return the Squared Normalization of a Multivector
		double squared_norm();

		//Return the Meet between two multivectors in a determined dimension
		Multivector _meet(Multivector& b, int dim);

		//Return the Join between two multivectors in a determined dimension
		Multivector _join(Multivector& b, int dim);

		//Return the Meet between two multivectors in a determined dimension
		Multivector meet(Multivector& b, int dim);

		//Return the Join between two multivectors in a determined dimension
		Multivector join(Multivector& b, int dim);

		//Return the Dual of a multivector, passing the dimension as a parameter
		Multivector dual(int);

		//Return the Undual of a multivector, passing the dimension as a parameter
		Multivector undual(int);

		//Return a Blade with the correspoding grade of a Multivector
		Multivector extract_grade(int);

		//Return the begin of the iterator of the hash map
		map<unsigned int, double>::iterator it_begin();

		//Return the end of the iterator of the hash map
		map<unsigned int, double>::iterator it_end();

		//The String based visualization
		void print();

		//The number based visualization
		void print_debug();

		//Rotor
		Multivector rotor(double angle);

		//Grade Involution
		Multivector grade_involution();

		//Check if it is a blade
		bool is_blade();

		//Check if 2 multivectors are equal
		//bool operator==(Multivector& b);

		//Check if 2 multivectors are equal
		bool equals(Multivector& b);

		//Parenthesis operator overloading to implicit know the coefficient of a basis given by the variable 'b'
		double& operator () (unsigned int b);

		//Parenthesis operator overloading to implicit know the coefficient of a basis given by the variable 'b'
		double& operator () (string b);

		//Parenthesis operator overloading to implicit know the coefficient of a basis given by the variable 'b'
		const double& operator () (string b) const;

		//Parenthesis operator overloading to attribute a coefficinet to a basis given by the variable 'b'
		const double& operator () (unsigned int b) const;

		//Set Metric
		void set_metric(mat m);

		//Get Metric
		mat get_metric();

	};

	//Return a multiplication between a Scalar and a multivector
	Multivector operator*(double s, Multivector& v);

	//Return the Identity given by the dimension dim
	Multivector I(int dim);

	//pow function between to integers i**j
	int pot(int i,int j);

	//Basis Orthogonalization Method
	vector<Multivector> _orthogonal_basis(vector<Multivector> b);

	//Blade Factorization
	vector<Multivector> _blade_factorization(Multivector& B);

	//Exponential of a blade
	Multivector rotor(Multivector& M, double angle);

	//Binary String to unsigned int
	unsigned int bin_str_to_int(string s);

	//Calculate the sign of two outer product basis
	double calc_sign (unsigned int bitmap1, unsigned int bitmap2);

	//Return the Meet between two multivectors in a determined dimension
	Multivector meet(Multivector& a, Multivector& b, int dim);

	//Return the Join between two multivectors in a determined dimension
	Multivector join(Multivector& a, Multivector& b, int dim);

	////////Return the Delta Product between 2 multivectors
	Multivector dp(Multivector& a, Multivector& b); 

	///////Return the Outer Product between 2 multivectors
	Multivector op(Multivector& a, Multivector& b); 

	//////////Return the addition of 2 multivectors
	Multivector add(Multivector& a, Multivector& b);

	/////////Return the subtraction of 2 multivectors
	Multivector sub(Multivector& a, Multivector& b);

	//////////Return the multiplication of 2 multivectors
	Multivector mul(Multivector& a, double b);

	/////////////Return the division of a multivectors and a scalar
	Multivector div(Multivector& a, double b);

	//////////////Return the regressive product of a multivectors and a scalar
	Multivector rp(Multivector& a, Multivector& b, int n);  

	/////////////Return the Geometric Product between 2 multivectors
	Multivector gp(Multivector& a, Multivector& b); 

	///////////Return the Left Contration between 2 multivectors
	Multivector lc(Multivector& a, Multivector& b);     

	////////////Return the Right Contration between 2 multivectors
	Multivector rc(Multivector& a, Multivector& b);   

	///////Return the Scalar between 2 multivectors
	double sc(Multivector& a, Multivector& b);

	//Calculate the sign of two outer product basis
	double canonical_reordering(unsigned int,unsigned int);

	//Count the number of '1' bits in a unsigned integer
	//Also calculate the grade of a blade
	unsigned int bit_count(unsigned int);

	//Check if 2 multivectors are equal
	bool operator==(Multivector& a, Multivector& b);

	//Check if 2 multivectors are equal
	bool equals(Multivector& a, Multivector& b);

	//Calculate the Logarithm

	//Bitmap to binary String
	string bitmap_to_bin_str(unsigned int);

	//Multivector Geometric Product
	Multivector ggp(Multivector& a, Multivector& b, mat space);

	//Basis convertion
	Multivector basis_convertion(Multivector& a, mat Q);

	//
	Multivector rp(Multivector& a, Multivector& b, Multivector& I);

	//class Metric
	//{
	//public:
	//	static mat metric;
	//};

	Multivector ex(Multivector&);
	Multivector sqr(Multivector&);
	Multivector po(Multivector&, double);
	Multivector lo(Multivector&);

}
